package shortbread;

import com.squareup.javapoet.*;

import javax.annotation.processing.Filer;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.NoType;
import java.io.IOException;
import java.util.*;

/**
 * 注解生成java类
 */
class CodeGenerator {
    private static final String EXTRA_METHOD = "shortbread_method";
    private final ClassName context = ClassName.get("ohos.app", "Context");
    private final ClassName shortcutInfo = ClassName.get("ohos.bundle", "ShortcutInfo");
    private final ClassName iOException = ClassName.get("java.io", "IOException");
    private final ClassName notExistException =
            ClassName.get("ohos.global.resource", "NotExistException");
    private final ClassName wrongTypeException =
            ClassName.get("ohos.global.resource", "WrongTypeException");
    private final ClassName ability = ClassName.get("ohos.aafwk.ability", "Ability");
    private final ClassName elementName = ClassName.get("ohos.bundle", "ElementName");
    private final ClassName list = ClassName.get("java.util", "List");
    private final ClassName shortcutIntent = ClassName.get("ohos.bundle", "ShortcutIntent");
    private final TypeName listOfShortcutInfo = ParameterizedTypeName.get(list, shortcutInfo);
    private final TypeName listOfListOfShortcutInfo = ParameterizedTypeName.get(list, listOfShortcutInfo);
    private final ClassName shortcutUtils = ClassName.get("shortbread", "ShortcutUtils");
    private Filer filer;
    private List<ShortcutAnnotatedElement> annotatedElements;

    CodeGenerator(final Filer filer, final List<ShortcutAnnotatedElement> annotatedElements) {
        this.filer = filer;
        this.annotatedElements = annotatedElements;
    }

    void generate() {
        TypeSpec shortbread = TypeSpec.classBuilder("ShortbreadGenerated")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(createShortcuts())
                .addMethod(callMethodShortcut())
                .build();
        JavaFile javaFile = JavaFile.builder("shortbread", shortbread)
                .indent("    ")
                .build();
        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private MethodSpec createShortcuts() {
        final MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("createShortcuts")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addException(iOException)
                .addException(notExistException)
                .addException(wrongTypeException)
                .returns(listOfListOfShortcutInfo)
                .addParameter(context, "context")
                .addStatement("$T<$T> enabledShortcuts = new $T<>()", List.class, shortcutInfo, ArrayList.class)
                .addStatement("$T<$T> disabledShortcuts = new $T<>()", List.class, shortcutInfo, ArrayList.class);
        for (int i = 0; i < annotatedElements.size(); i++) {
            Shortcut shortcut = annotatedElements.get(i).getShortcut();
            methodBuilder.addStatement("$T shortcutInfo$L = new $T()", shortcutInfo, i, shortcutInfo);
            methodBuilder.addCode(createShortcuts(annotatedElements.get(i), i));
            methodBuilder.addCode("\n$L.add(", shortcut.enabled() ? "enabledShortcuts" : "disabledShortcuts");
            methodBuilder.addCode("shortcutInfo$L", i);
            methodBuilder.addStatement(")\n");
        }
        return methodBuilder
                .addStatement("return $T.asList(enabledShortcuts, disabledShortcuts)", Arrays.class)
                .build();
    }

    private void createLabel(ShortcutAnnotatedElement annotatedElement,Shortcut shortcut,
                             CodeBlock.Builder blockBuilder,int index){
        if (!"".equals(shortcut.shortLabel())) {
            blockBuilder.add("shortcutInfo$L.setLabel($S);\n", index, shortcut.shortLabel());
        } else if (shortcut.shortLabelRes() != 0) {
            blockBuilder.add("try{\n");
            blockBuilder.add("shortcutInfo$L.setLabel(context.getResourceManager().getElement($L).getString());\n",
                    index, shortcut.shortLabelRes());
            blockBuilder.add(" } catch ($T e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        } catch ($T e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        } catch ($T e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        }\n", iOException, notExistException, wrongTypeException);
        } else {
            blockBuilder.add("shortcutInfo$L.setLabel($T.getActivityLabel(context, $T.class).toString());\n",
                    index, shortcutUtils,
                    ClassName.bestGuess(annotatedElement.getClassName()));
        }
    }

    private void createIcon(Shortcut shortcut,CodeBlock.Builder blockBuilder, int index){
        if (shortcut.icon() != 0) {
            blockBuilder.add("try{\n");
            blockBuilder.add("shortcutInfo$L.setIcon(context.getResourceManager().getMediaPath($L));\n",
                    index, shortcut.icon());
            blockBuilder.add(" } catch (IOException e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        } catch (NotExistException e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        } catch (WrongTypeException e) {\n" +
                    "            e.printStackTrace();\n" +
                    "        }\n");
        }
    }
    private void createDisabledMessage(Shortcut shortcut,CodeBlock.Builder blockBuilder, int index){
        if (!"".equals(shortcut.disabledMessage())) {
            blockBuilder.add("shortcutInfo$L.setDisableMessage($S);\n", index, shortcut.disabledMessage());
        } else {
            if (shortcut.disabledMessageRes() != 0) {
                blockBuilder.add("try{\n");
                blockBuilder.add(
                        "shortcutInfo$L.setDisabledMessage(context.getResourceManager().getElement($L).getString());\n",
                        index, shortcut.disabledMessageRes());
                blockBuilder.add(" } catch (IOException e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        } catch (NotExistException e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        } catch (WrongTypeException e) {\n" +
                        "            e.printStackTrace();\n" +
                        "        }\n");
            }
        }
    }
    private CodeBlock createShortcuts(ShortcutAnnotatedElement annotatedElement, int index) {
        Shortcut shortcut = annotatedElement.getShortcut();
        final CodeBlock.Builder blockBuilder = CodeBlock.builder();
        blockBuilder.add(" shortcutInfo$L.setId($S);\n", index, shortcut.id())
                .indent()
                .indent();
        createLabel(annotatedElement,shortcut,blockBuilder,index);
        createIcon(shortcut,blockBuilder,index);
        createDisabledMessage(shortcut,blockBuilder,index);
        try {
            shortcut.ability();
        } catch (MirroredTypeException mte) {
            if (!(mte.getTypeMirror() instanceof NoType)) {
                DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
                TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                String targetActivityClassName = classTypeElement.getQualifiedName().toString();
                blockBuilder.add(".setActivity(new $T(context, $T.class));\n", elementName,
                        ClassName.bestGuess(targetActivityClassName));
            }
        }
        blockBuilder.add("shortcutInfo$L.setBundleName(context.getBundleName());\n", index);
        blockBuilder.add("shortcutInfo$L.setHostAbilityName($S);\n", index, shortcut.hostAbility());
        blockBuilder.unindent().unindent();
        final List<? extends AnnotationMirror> annotationMirrors =
                annotatedElement.getElement().getAnnotationMirrors();
        blockBuilder.add(createShortcutIntent(index, annotationMirrors));
        final ClassName activityClassName = ClassName.get((annotatedElement.getActivity()));
        blockBuilder.add("$T shortcutIntent$L = new $T(context.getBundleName(),$S);\n",
                shortcutIntent, index, shortcutIntent, activityClassName);
        if (annotatedElement instanceof ShortcutAnnotatedMethod) {
            blockBuilder.indent().indent();
            blockBuilder.add("shortcutIntent$L.addParam($S, $S);\n",
                    index, EXTRA_METHOD,
                    ((ShortcutAnnotatedMethod) annotatedElement).getMethodName());
        }
        blockBuilder.addStatement("shortcutIntents$L.add(shortcutIntent$L);", index, index);
        blockBuilder.addStatement("shortcutInfo$L.setIntents(shortcutIntents$L)", index, index);
        blockBuilder.indent().indent();
        return blockBuilder.unindent().unindent().build();
    }

    private CodeBlock createShortcutIntent(int shortcutIndex, List<? extends AnnotationMirror> annotationMirrors) {
        final CodeBlock.Builder blockBuilder = CodeBlock.builder();
        blockBuilder.addStatement("$T<$T> shortcutIntents$L = new $T<>()",
                List.class, shortcutIntent, shortcutIndex, ArrayList.class);
        for (int j = 0; j < annotationMirrors.size(); j++) {
            AnnotationMirror annotationMirror = annotationMirrors.get(j);
            if (annotationMirror.getAnnotationType().toString().equals(Shortcut.class.getName())) {
                int k = 0;
                for (Map.Entry<? extends Element,
                        ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
                    k++;
                    if ("backStack".equals(entry.getKey().getSimpleName().toString())) {
                        final String value = entry.getValue().getValue().toString();
                        if (!"".equals(value.trim())) {
                            int h = 0;
                            for (final String backstackActivityClassName : value.split(",")) {
                                h++;
                                blockBuilder.addStatement("$T shortcutIntent$L$L$L$L = new $T();",
                                        shortcutIntent, shortcutIndex, j, k, h, shortcutIntent);
                                blockBuilder.add("shortcutIntent$L$L$L$L.setTargetBundle(context.getBundleName());",
                                        shortcutIndex, j, k, h);
                                blockBuilder.add("shortcutIntent$L$L$L$L.setTargetClass($S);\n",
                                        shortcutIndex, j, k, h,
                                        backstackActivityClassName.replace(".class", ""));
                                blockBuilder.add("shortcutIntents$L.add(", shortcutIndex);
                                blockBuilder.add("shortcutIntent$L$L$L$L", shortcutIndex, j, k, h);
                                blockBuilder.addStatement(")\n");
                            }
                        }
                    }
                }
            }
        }
        return blockBuilder.indent().indent().build();
    }

    private MethodSpec callMethodShortcut() {
        HashMap<String, List<String>> classMethodsMap = new HashMap<>();
        for (final ShortcutAnnotatedElement annotatedElement : annotatedElements) {
            if (annotatedElement instanceof ShortcutAnnotatedMethod) {
                final ShortcutAnnotatedMethod annotatedMethod = (ShortcutAnnotatedMethod) annotatedElement;
                if (classMethodsMap.containsKey(annotatedMethod.getClassName())) {
                    classMethodsMap.get(annotatedElement.getClassName()).add(annotatedMethod.getMethodName());
                } else {
                    classMethodsMap.put(annotatedMethod.getClassName(), new ArrayList<String>() {{
                        add(annotatedMethod.getMethodName());
                    }});
                }
            }
        }
        final MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("callMethodShortcut")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addParameter(ability, "ability");
        for (final Map.Entry<String, List<String>> annotatedMethodName : classMethodsMap.entrySet()) {
            ClassName activityClassName = ClassName.bestGuess(annotatedMethodName.getKey());
            List<String> methodNames = annotatedMethodName.getValue();
            methodBuilder.beginControlFlow("if (ability instanceof $T)", activityClassName);
            for (final String methodName : methodNames) {
                methodBuilder.beginControlFlow("if ($S.equals(ability.getIntent().getStringParam($S)))",
                        methodName, EXTRA_METHOD);
                methodBuilder.addStatement("(($T) ability).$L()", activityClassName, methodName);
                methodBuilder.endControlFlow();
            }
            methodBuilder.endControlFlow();
        }
        return methodBuilder.build();
    }
}
